////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNStrings/inc/WNStrings.h"
#include "WNScripting/inc/WNTypeManager.h"
#include "WNScripting/inc/WNIdentifierMap.h"
#include "WNScripting/inc/WNScriptTypeFunctions.h"
#include "WNScripting/inc/WNScriptVariable.h"
#include "WNScripting/inc/WNScriptingEngineData.h"
#include "WNScripting/inc/WNLLVMHelpers.h"

#include <llvm/Intrinsics.h>

#define INITIALIZE_TYPE(_var, _WNVarType, _llvmType, _referenceType, _forceheap) \
    { \
         _var.mType = _WNVarType; \
         _var.mUserTypeName = WNInvalidIdentifier; \
         _var.mLlvmType = _llvmType; \
         _var.mSubType = WN_NULL; \
         _var.mStructTypes = WN_NULL; \
         _var.mSubTypeNames = WN_NULL; \
         _var.mIsStruct = _referenceType; \
         _var.mForceHeap = _forceheap; \
    }

using namespace WNStrings;
using namespace WNMemory;
using namespace WNScripting;

//Built-in types (static)
static __WNTypeElementInternal sIntType;
static __WNTypeElementInternal sFloatType;
static __WNTypeElementInternal sBoolType;
static __WNTypeElementInternal sArrayType;
static __WNTypeElementInternal sStructType;
static __WNTypeElementInternal sCharType;


static llvm::CmpInst::Predicate sIntPredicates[] = {
 //== != < > <= >=
    llvm::CmpInst::ICMP_EQ, llvm::CmpInst::ICMP_NE, llvm::CmpInst::ICMP_SLT, llvm::CmpInst::ICMP_SGT, llvm::CmpInst::ICMP_SLE, llvm::CmpInst::ICMP_SLE
};

static llvm::CmpInst::Predicate sFloatPredicates[] = {
 //== != < > <= >=
     llvm::CmpInst::FCMP_OEQ, llvm::CmpInst::FCMP_ONE, llvm::CmpInst::FCMP_OLT, llvm::CmpInst::FCMP_OGT, llvm::CmpInst::FCMP_OLE, llvm::CmpInst::FCMP_OLE
};

struct __WNStaticTypeNames{
    WNTypeElement type;
    const WN_CHAR * name;
};

static __WNStaticTypeNames sRegisteredTypeNames[] = {
    {&sIntType, "Int"},
    {&sFloatType, "Float"},
    {&sBoolType, "Bool"},
    {&sArrayType, "Array"},
    {&sStructType, "Struct"},
    {&sCharType, "Char"}
};

static WNNativeElementType sRegisteredNativeTypes[] = {
    {&sIntType, "WN_INT32", 'I', WN_FALSE},
    {&sFloatType, "WN_FLOAT32", 'F', WN_FALSE},
    {&sBoolType, "WN_BOOL", 'B', WN_FALSE},
    {&sStructType, "WN_POINTER", 'P', WN_FALSE},
    {&sCharType, "WN_CHAR", 'C', WN_FALSE},
    {&sCharType, "WN_VOID", 'V', WN_FALSE}
};

//
/////////////////////////////////////////// Static Functions

llvm::Type* WNTypeManager::GetLLVMType(WNTypeElement _type) const {
    return(_type->mLlvmType);
}

WN_VOID WNInvalidArithmetic(WN_CHAR*, WN_CHAR*, WNStaticArithmetic, WNStaticPredicate, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR*) {
    _outCast = eWNCastInvalidArithmetic;
    _outType = WN_NULL;
    return;
}

WN_BOOL WNInvalidTypeCheck(WN_CHAR*) {
    return(WN_FALSE);
}

WNCastAllowance WNInvalidDynamicArithmetic(WNStaticArithmetic, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _elem) {
    _elem = WN_NULL;
    return(eWNCastInvalidArithmetic);
}

WNCastAllowance WNEqualDynamicArithmetic(WNStaticArithmetic _arith, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _elem) {
    if (_arith == eWNStaticAssign) {
        return(eWNCastOk);
    }
    _elem = WN_NULL;
    return(eWNCastInvalidArithmetic);
}

llvm::Value* WNInvalidLLVMOperation(llvm::Value*, llvm::Value*, const WNTypeElement&, WNStaticArithmetic, WNStaticPredicate, WNScriptingEngineData&) {
    return(WN_NULL);
}

llvm::Value* WNInvalidLLVMCast(llvm::Value* _val, llvm::IRBuilderBase&) {
    return(WN_NULL);
}

llvm::Value* WNEqualLLVMCast(llvm::Value* _val, llvm::IRBuilderBase&) {
    return(_val);
}

llvm::Value* WNInvalidConstFunction(WNScriptingEngineData& _data, WN_CHAR* _constValue) {
    return(WN_NULL);
}

//TODO: Definitely get all of this out of here into it's own header.. this is large, and clunky
//      possibly more than 1 header, (Arithmetic, Verification, 
WN_VOID WNIntFloatArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    _outCast = eWNCastLossPrecision;
    WNS_INT_CONST* num1 = reinterpret_cast<WNS_INT_CONST*>(_elem1);
    WNS_FLOAT_CONST* num2 = reinterpret_cast<WNS_FLOAT_CONST*>(_elem2);
    switch(_arithType){
        case eWNStaticAdd: {
                WNS_FLOAT_CONST n = *num1 + *num2;
                _outType = &sFloatType;
                (*(WNS_FLOAT_CONST*)_outLoc) = n;
                return;
            }
            break;
        case eWNStaticSubtract: {
                WNS_FLOAT_CONST n = *num1 - *num2;
                _outType = &sFloatType;
                (*(WNS_FLOAT_CONST*)_outLoc) = n;
                return;
            }
            break;
        case eWNStaticMultiply: {
                WNS_FLOAT_CONST n = *num1 * *num2;
                _outType = &sFloatType;
                (*(WNS_FLOAT_CONST*)_outLoc) = n;
                return;
            }
        case eWNStaticMod: {
                WNS_FLOAT_CONST n = fmodf(static_cast<WNS_FLOAT_CONST>(*num1),*num2);
                _outType = &sFloatType;
                (*(WNS_FLOAT_CONST*)_outLoc) = n;
                return;
            }
            break;
        case eWNStaticDivide: {
                WNS_FLOAT_CONST n = *num1 / *num2;
                _outType = &sFloatType;
                (*(WNS_FLOAT_CONST*)_outLoc) = n;
                return;
            }
            break;
        case eWNStaticAssign: {
                WNS_INT_CONST n = (WNS_INT_CONST)*num2;
                _outType = &sIntType;
                _outCast = eWNCastLossPrecision;
                (*(WNS_INT_CONST*)_outLoc) = n;
                return;
            }
            break;
        case eWNStaticEquality: {
            _outType = &sBoolType;
            _outCast = eWNCastLossPrecision;
            WNS_FLOAT_CONST val1 = (WNS_FLOAT_CONST)(*num1);
            WNS_FLOAT_CONST val2 = (WNS_FLOAT_CONST)(*num2);
            switch (_pred) {
            case eWNEqual:
                (*(WNS_CHAR_CONST*)_outLoc) = ((val1 == val2)? 1 : 0);
                return;
                break;
            case eWNNotEqual:
                (*(WNS_CHAR_CONST*)_outLoc) = ((val1 != val2)? 1 : 0);
                return;
                break;
            case eWNLessThan:
                (*(WNS_CHAR_CONST*)_outLoc) = ((val1 < val2)? 1 : 0);
                return;
                break;
            case eWNGreaterThan:
                (*(WNS_CHAR_CONST*)_outLoc) = ((val1 > val2)? 1 : 0);
                return;
                break;
            case eWNLessThanOrEqual:
                (*(WNS_CHAR_CONST*)_outLoc) = ((val1 <= val2)? 1 : 0);
                return;
                break;
            case eWNGreaterThanOrEqual:
                (*(WNS_CHAR_CONST*)_outLoc) = ((val1 >= val2)? 1 : 0);
                return;
                break;
            }
        }
        break;
    }
    _outCast = eWNCastInvalidArithmetic;
    return;
}

WN_VOID WNFloatIntArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    _outCast = eWNCastLossPrecision;
    WNS_FLOAT_CONST* num1 = reinterpret_cast<WNS_FLOAT_CONST*>(_elem1);
    WNS_INT_CONST* num2 = reinterpret_cast<WNS_INT_CONST*>(_elem2);
    switch(_arithType) {
        case eWNStaticAdd: {
            WNS_FLOAT_CONST n = *num1 + *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticSubtract: {
            WNS_FLOAT_CONST n = *num1 - *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticMultiply: {
            WNS_FLOAT_CONST n = *num1 * *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticMod: {
            WNS_FLOAT_CONST n = fmodf(*num1,static_cast<WNS_FLOAT_CONST>(*num2));
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticDivide: {
            WNS_FLOAT_CONST n = *num1 / *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticAssign: {
            WNS_FLOAT_CONST n = (WNS_FLOAT_CONST)*num2;
            _outType = &sFloatType;
            _outCast = eWNCastLossPrecision;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticEquality: {
            _outType = &sBoolType;
            _outCast = eWNCastLossPrecision;
            WNS_FLOAT_CONST val1 = (WNS_FLOAT_CONST)(*num1);
            WNS_FLOAT_CONST val2 = (WNS_FLOAT_CONST)(*num2);
            switch (_pred){
                case eWNEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 == val2)? 1 : 0);
                    return;
                    break;
                case eWNNotEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 != val2)? 1 : 0);
                    return;
                    break;
                case eWNLessThan:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 < val2)? 1 : 0);
                    return;
                    break;
                case eWNGreaterThan:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 > val2)? 1 : 0);
                    return;
                    break;
                case eWNLessThanOrEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 <= val2)? 1 : 0);
                    return;
                    break;
                case eWNGreaterThanOrEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 >= val2)? 1 : 0);
                    return;
                    break;
            }
        }
        break;
    }
    _outCast = eWNCastInvalidArithmetic;
    return;
}

WN_VOID WNCharFloatArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    WN_CHAR vals[WN_SCRIPTING_MAX_CONST_SIZE];
    WNS_CHAR_CONST c = *(WNS_CHAR_CONST*)_elem1;
    WNS_INT_CONST i = c;
    (*(WNS_INT_CONST*)&vals[0]) = i;

    WNIntFloatArithmetic(vals, _elem2, _arithType, _pred, _outType, _outCast, _outLoc);
    if (_outType == &sIntType) {
        i = *(WNS_INT_CONST*)_outLoc;
        c = static_cast<WNS_CHAR_CONST>(i);
        (*(WNS_CHAR_CONST*)&_outLoc[0]) = c;
        _outType = &sCharType;
    }
    return;
}

WN_VOID WNFloatCharArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    WN_CHAR vals[WN_SCRIPTING_MAX_CONST_SIZE];
    WNS_CHAR_CONST c = *(WNS_CHAR_CONST*)_elem2;
    WNS_INT_CONST i = c;
    (*(WNS_INT_CONST*)&vals[0]) = i;

    WNFloatIntArithmetic(_elem1, vals, _arithType, _pred, _outType, _outCast, _outLoc);
    if (_outType == &sIntType) {
        i = *(WNS_INT_CONST*)_outLoc;
        c = static_cast<WNS_CHAR_CONST>(i);
        (*(WNS_CHAR_CONST*)&_outLoc[0]) = c;
        _outType = &sCharType;
    }
    return;
}


WN_VOID WNIntIntArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    _outCast = eWNCastOk;
    WNS_INT_CONST* num1 = reinterpret_cast<WNS_INT_CONST*>(_elem1);
    WNS_INT_CONST* num2 = reinterpret_cast<WNS_INT_CONST*>(_elem2);
    switch(_arithType) {
        case eWNStaticAdd: {
            WNS_INT_CONST n = *num1 + *num2;
            _outType = &sIntType;
            (*(WNS_INT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticSubtract: {
            WNS_INT_CONST n = *num1 - *num2;
            _outType = &sIntType;
            (*(WNS_INT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticMultiply: {
            WNS_INT_CONST n = *num1 * *num2;
            _outType = &sIntType;
            (*(WNS_INT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticMod: {
            WNS_INT_CONST n = *num1 % *num2;
            _outType = &sIntType;
            (*(WNS_INT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticDivide: {
             WNS_INT_CONST n = *num1 / *num2;
             _outType = &sIntType;
             (*(WNS_INT_CONST*)_outLoc) = n;
             return;
        }
        break;
        case eWNStaticAssign: {
            WNS_INT_CONST n = *num2;
            _outType = &sIntType;
            (*(WNS_INT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticEquality: {
            _outType = &sBoolType;
            _outCast = eWNCastLossPrecision;
            WNS_INT_CONST val1 = (WNS_INT_CONST)(*num1);
            WNS_INT_CONST val2 = (WNS_INT_CONST)(*num2);
            switch (_pred) {
                case eWNEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 == val2)? 1 : 0);
                    return;
                    break;
                case eWNNotEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 != val2)? 1 : 0);
                    return;
                    break;
                case eWNLessThan:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 < val2)? 1 : 0);
                    return;
                    break;
                case eWNGreaterThan:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 > val2)? 1 : 0);
                    return;
                    break;
                case eWNLessThanOrEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 <= val2)? 1 : 0);
                    return;
                    break;
                case eWNGreaterThanOrEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 >= val2)? 1 : 0);
                    return;
                    break;
            }
        }
        break;
    }
    _outCast = eWNCastInvalidArithmetic;
    return;
}

WN_VOID WNCharCharArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    _outCast = eWNCastOk;

    WN_CHAR val1[WN_SCRIPTING_MAX_CONST_SIZE];
    WNS_CHAR_CONST c1 = *(WNS_CHAR_CONST*)_elem1;
    WNS_INT_CONST i1 = c1;
    (*(WNS_INT_CONST*)&val1[0]) = i1;

    WN_CHAR val2[WN_SCRIPTING_MAX_CONST_SIZE];
    WNS_CHAR_CONST c2 = *(WNS_CHAR_CONST*)_elem2;
    WNS_INT_CONST i2 = c2;
    (*(WNS_INT_CONST*)&val2[0]) = i2;

    WNIntIntArithmetic(val1, val2, _arithType, _pred, _outType, _outCast, _outLoc);
    if(_outType == &sIntType) {
        _outType = &sCharType;
        i1 = *reinterpret_cast<WNS_INT_CONST*>(_outLoc);
        if(i1 > 0xFF) {
            _outCast = eWNCastLossPrecision;
        }
        (i1) &= 0xFF;
        c1 = static_cast<WNS_CHAR_CONST>(i1);
        (*(WNS_CHAR_CONST*)&_outLoc[0]) = c1;
    }
    return;
}

WN_VOID WNIntCharArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    _outCast = eWNCastOk;

    WN_CHAR val[WN_SCRIPTING_MAX_CONST_SIZE];
    WNS_CHAR_CONST c = *(WNS_CHAR_CONST*)_elem2;
    WNS_INT_CONST i = c;
    (*(WNS_INT_CONST*)&val[0]) = i;

    WNIntIntArithmetic(_elem1, val, _arithType, _pred, _outType, _outCast, _outLoc);
    return;
}

WN_VOID WNCharIntArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    _outCast = eWNCastOk;
    WN_CHAR val[WN_SCRIPTING_MAX_CONST_SIZE];
    WNS_CHAR_CONST c = *(WNS_CHAR_CONST*)_elem1;
    WNS_INT_CONST i = c;
    (*(WNS_INT_CONST*)&val[0]) = i;

    WNIntIntArithmetic(val, _elem2, _arithType, _pred, _outType, _outCast, _outLoc);
    if(_arithType == eWNStaticAssign) {
        i = *reinterpret_cast<WNS_INT_CONST*>(_outLoc);

        if(i > 0xFF) {
            _outCast = eWNCastLossPrecision;
        }
        (i) &= 0xFF;
        c = static_cast<WNS_CHAR_CONST>(i);
        (*(WNS_CHAR_CONST*)&_outLoc[0]) = c;
    }
    return;
}

WN_VOID WNFloatFloatArithmetic(WN_CHAR* _elem1, WN_CHAR* _elem2, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNTypeElement& _outType, WNCastAllowance& _outCast, WN_CHAR* _outLoc) {
    _outCast = eWNCastOk;
    WNS_FLOAT_CONST* num1 = reinterpret_cast<WNS_FLOAT_CONST*>(_elem1);
    WNS_FLOAT_CONST* num2 = reinterpret_cast<WNS_FLOAT_CONST*>(_elem2);
    switch(_arithType) {
        case eWNStaticAdd: {
            WNS_FLOAT_CONST n = *num1 + *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticSubtract: {
            WNS_FLOAT_CONST n = *num1 - *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticMultiply: {
            WNS_FLOAT_CONST n = *num1 * *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticMod: {
            WNS_FLOAT_CONST n = fmodf(*num1,*num2);
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticDivide: {
            WNS_FLOAT_CONST n = *num1 / *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticAssign: {
            WNS_FLOAT_CONST n = *num2;
            _outType = &sFloatType;
            (*(WNS_FLOAT_CONST*)_outLoc) = n;
            return;
        }
        break;
        case eWNStaticEquality: {
            _outType = &sBoolType;
            _outCast = eWNCastLossPrecision;
            WNS_FLOAT_CONST val1 = (WNS_FLOAT_CONST)(*num1);
            WNS_FLOAT_CONST val2 = (WNS_FLOAT_CONST)(*num2);
            switch (_pred) {
                case eWNEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 == val2)? 1 : 0);
                    return;
                    break;
                case eWNNotEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 != val2)? 1 : 0);
                    return;
                    break;
                case eWNLessThan:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 < val2)? 1 : 0);
                    return;
                    break;
                case eWNGreaterThan:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 > val2)? 1 : 0);
                    return;
                    break;
                case eWNLessThanOrEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 <= val2)? 1 : 0);
                    return;
                    break;
                case eWNGreaterThanOrEqual:
                    (*(WNS_CHAR_CONST*)_outLoc) = ((val1 >= val2)? 1 : 0);
                    return;
                    break;
            }
        }
        break;
    }
    _outCast = eWNCastInvalidArithmetic;
    return;
}

WNCastAllowance WNIntFloatVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sFloatType;
    if (_arithType == eWNStaticAssign) {
        _outType = &sIntType;
        return eWNCastLossPrecision;
    }
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastLossPrecision);
}

WNCastAllowance WNCharFloatVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sFloatType;
    if (_arithType == eWNStaticAssign) {
        _outType = &sCharType;
        return eWNCastLossPrecision;
    }
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastLossPrecision);
}

WNCastAllowance WNFloatIntVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sFloatType;
    if (_arithType == eWNStaticAssign) {
        return eWNCastLossPrecision;
    }
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastLossPrecision);
}

WNCastAllowance WNFloatCharVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sFloatType;
    if (_arithType == eWNStaticAssign) {
        return eWNCastLossPrecision;
    }
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastLossPrecision);
}

WNCastAllowance WNIntIntVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sIntType;
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastOk);
}

WNCastAllowance WNCharIntVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sIntType;
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    } else if (_arithType == eWNStaticAssign) {
        _outType = &sCharType;
        return(eWNCastLossPrecision);
    }
    return(eWNCastOk);
}

WNCastAllowance WNIntCharVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sIntType;
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastOk);
}

WNCastAllowance WNCharCharVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sCharType;
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastOk);
}

WNCastAllowance WNFloatFloatVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement&, const WNTypeElement&, WNTypeElement& _outType) {
    _outType = &sFloatType;
    if (_arithType == eWNStaticEquality) {
        _outType = &sBoolType;
    }
    return(eWNCastOk);
}

WNCastAllowance WNArrayIntVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement& _elem1, const WNTypeElement& _elem2, WNTypeElement& _outType) {
    if (_elem2 != &sIntType) {
        return(eWNCastFail);
    }
    if (_arithType == eWNStaticBrackets) {
        _outType = _elem1->mSubType;
    } else {
        return(eWNCastFail);
    }
    return(eWNCastOk);
}

WNCastAllowance WNArrayCharVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement& _elem1, const WNTypeElement& _elem2, WNTypeElement& _outType) {
    if (_elem2 != &sCharType) {
        return(eWNCastFail);
    }
    if (_arithType == eWNStaticBrackets) {
        _outType = _elem1->mSubType;
    } else {
        return(eWNCastFail);
    }
    return(eWNCastOk);
}

WNCastAllowance WNStructStructVerifyArithmetic(WNStaticArithmetic _arithType, const WNTypeElement& _elem1, const WNTypeElement& _elem2, WNTypeElement& _outType) {
    if(_arithType == eWNStaticAssign) {
        _outType = _elem1;
    } else {
        return(eWNCastFail);
    }

    return(eWNCastOk);
}

llvm::Value* WNCreateFloatConstant(WNScriptingEngineData& _data, WN_CHAR* _constValue) {
    WNS_FLOAT_CONST val = *(WNS_FLOAT_CONST*)(_constValue);
    return(llvm::ConstantFP::get(sFloatType.mLlvmType, val));
}

llvm::Value* WNCreateIntConstant(WNScriptingEngineData& _data, WN_CHAR* _constValue) {
    WNS_INT_CONST val = *(WNS_INT_CONST*)(_constValue);
    return(llvm::ConstantInt::get(sIntType.mLlvmType, val));
}

llvm::Value* WNCreateCharConstant(WNScriptingEngineData& _data, WN_CHAR* _constValue) {
    WNS_CHAR_CONST val = *(WNS_CHAR_CONST*)(_constValue);
    return(llvm::ConstantInt::get(sCharType.mLlvmType, val));
}

llvm::Value* WNArrayIntLLVMGeneration(llvm::Value * _arg1, llvm::Value* _elem2, const WNTypeElement& _firstType, WNStaticArithmetic __arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    if (__arithType != eWNStaticBrackets) {
        return(WN_NULL);
    }
#ifdef WN_SCRIPTING_ENABLE_BOUNDS_CHECKING
    
    llvm::Value* val = builder->CreatePointerCast(_arg1, llvm::PointerType::getUnqual(_scriptingData.mTypeManager->GetType(eWNTypeInt)->mLlvmType));
    val = builder->CreateLoad(val, WN_FALSE, "arraySize");
    llvm::Value* cmp = builder->CreateICmpULT(_elem2, val, "array bounds check");
    llvm::BasicBlock* outOfBounds = llvm::BasicBlock::Create(llvm::getGlobalContext(), "arrayOutOfBounds", _scriptingData.mScopedVariableList->GetCurrentFunction()->mLLVMFunction); // NEED TO PASS FUNCTION DOWN (probably use script data instead of builder :|
    llvm::BasicBlock* boundsOk = llvm::BasicBlock::Create(llvm::getGlobalContext(), "arrayInBounds", _scriptingData.mScopedVariableList->GetCurrentFunction()->mLLVMFunction);
    builder->CreateCondBr(cmp, boundsOk, outOfBounds);
    builder->SetInsertPoint(outOfBounds);
    llvm::Value* nullVal = llvm::ConstantInt::get(GET_DATA_LAYOUT(_scriptingData.mExecutionEngine)->GET_INT_PTR_TYPE(llvm::getGlobalContext()), 0);
    nullVal = builder->CreateIntToPtr(nullVal, _scriptingData.mTypeManager->GetType(eWNTypeInt)->mLlvmType->getPointerTo(0));
    llvm::Value* crashVal = builder->CreateLoad(nullVal, WN_FALSE, "");
    builder->CreateStore(crashVal, nullVal, WN_FALSE);
    builder->CreateBr(boundsOk);
    builder->SetInsertPoint(boundsOk);

    WN_INT32 tempVal = WNLLVMGetTypesPerType(_scriptingData, _scriptingData.mTypeManager->GetType(eWNTypeInt)->mLlvmType, _scriptingData.mTypeManager->GetSubType(_firstType)->mLlvmType);
    
    llvm::Value* lookupVal = llvm::ConstantInt::get(builder->getInt32Ty(), tempVal);
    _elem2 = builder->CreateIntCast(_elem2, builder->getInt32Ty(), WN_FALSE);
    _elem2 = builder->CreateAdd(_elem2, lookupVal);
    llvm::Value* gepVal = builder->CreateGEP(_arg1, _elem2);

    llvm::Value* retVal = builder->CreateLoad(gepVal);
#else
#error still need to implement non-bounds checked arrays
#endif
    return(retVal);
}

llvm::Value* WNArrayCharLLVMGeneration(llvm::Value* _arg1, llvm::Value* _elem2, const WNTypeElement& _firstType, WNStaticArithmetic __arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    _elem2 = builder->CreateIntCast(_elem2, _scriptingData.mTypeManager->GetType(eWNTypeInt)->mLlvmType, WN_FALSE);
    return(WNArrayIntLLVMGeneration(_arg1, _elem2, _firstType, __arithType, _pred, _scriptingData));
}

llvm::Value* WNStructStructLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _firstType, WNStaticArithmetic __arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    return(WN_NULL);
}

llvm::Value* WNFloatFloatLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _elemType, WNStaticArithmetic __arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    switch(__arithType) {
    case eWNStaticAdd:
        return(builder->CreateFAdd(_arg1, _arg2, "floatAdd"));
        break;
    case eWNStaticDivide:
        return(builder->CreateFDiv(_arg1, _arg2, "floatDiv"));
        break;
    case eWNStaticMultiply:
        return(builder->CreateFMul(_arg1, _arg2, "floatMul"));
        break;
    case eWNStaticMod:
        return(builder->CreateFRem(_arg1, _arg2, "floatMod"));
        break;
    case eWNStaticSubtract:
        return(builder->CreateFSub(_arg1, _arg2, "floatSub"));
        break;
    case eWNStaticAssign:
        return(WN_NULL);
        break;
    case eWNStaticEquality:
        return(builder->CreateFCmp(sFloatPredicates[_pred], _arg1, _arg2, "floatCompare"));
        break;
    }
    return(WN_NULL);
}

//TODO: Once we have a global state for initializing the Scripting Engine
//      Allow a global (undefined signed/unsigned overflow/underflow)
//      this can give us performance if no-one over/underflows (but we may just want to keep "correct"
//      wrapping in place. for now wrap
//      If we do this, also do this in WNLValue
//TODO: Once we have global state, add option for exact vs: inexact Division
llvm::Value* WNIntIntLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement&, WNStaticArithmetic __arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    switch(__arithType) {
        case eWNStaticAdd:
            return(builder->CreateAdd(_arg1, _arg2, "intAd"));
            break;
        case eWNStaticDivide:
            return(builder->CreateSDiv(_arg1, _arg2, "intDiv", WN_TRUE)); //WN_TRUE == exact
            break;
        case eWNStaticMultiply:
            return(builder->CreateMul(_arg1, _arg2, "intMul"));
            break;
        case eWNStaticMod:
            return(builder->CreateSRem(_arg1, _arg2, "intMod"));
            break;
        case eWNStaticSubtract:
            return(builder->CreateSub(_arg1, _arg2, "intSub"));
            break;
        case eWNStaticEquality:
            return(builder->CreateICmp(sIntPredicates[_pred], _arg1, _arg2, "intCompare"));
            break;
    }
    return(WN_NULL);
}

llvm::Value* WNCharCharLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _type, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    return(WNIntIntLLVMGeneration(_arg1, _arg2, _type, _arithType, _pred, _scriptingData));
}

llvm::Value* WNIntCharLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _type, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    _arg2 = builder->CreateIntCast(_arg2, _arg1->getType(), WN_TRUE);
    return(WNIntIntLLVMGeneration(_arg1, _arg2, _type, _arithType, _pred, _scriptingData));
}

llvm::Value* WNCharIntLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _type, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    _arg1 = builder->CreateIntCast(_arg1, _arg2->getType(), WN_TRUE);
    return(WNIntIntLLVMGeneration(_arg1, _arg2, _type, _arithType, _pred, _scriptingData));
}

llvm::Value* WNIntFloatLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2,  const WNTypeElement& _elemType, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    if (_arithType != eWNStaticAssign) {
        llvm::Value* tempVal = builder->CreateCast(llvm::Instruction::SIToFP, _arg1, _arg2->getType(), "castFirstToFloat");
        return(WNFloatFloatLLVMGeneration(tempVal, _arg2, _elemType, _arithType, _pred, _scriptingData));
    }
    return(WN_NULL);
}

llvm::Value* WNCharFloatLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _type, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNScriptingEngineData& scriptingData) {
    return(WNIntFloatLLVMGeneration(_arg1, _arg2, _type, _arithType, _pred, scriptingData));
}

llvm::Value* WNFloatIntLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _elemType, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNScriptingEngineData& scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(scriptingData.mBuilder);
    if(_arithType != eWNStaticAssign) {
        llvm::Value* tempVal = builder->CreateCast(llvm::Instruction::SIToFP, _arg2, _arg1->getType(), "castSecondToFloat");
        return(WNFloatFloatLLVMGeneration(_arg1, tempVal, _elemType, _arithType, _pred, scriptingData));
    }
    return(WN_NULL);
}

llvm::Value* WNFloatCharLLVMGeneration(llvm::Value* _arg1, llvm::Value* _arg2, const WNTypeElement& _type, WNStaticArithmetic _arithType, WNStaticPredicate _pred, WNScriptingEngineData& scriptingData) {
    return(WNFloatIntLLVMGeneration(_arg1, _arg2, _type, _arithType, _pred, scriptingData));
}

llvm::Value* WNIntToFloatCast(llvm::Value* _val, llvm::IRBuilderBase& _builder) {
    llvm::Value* floatVal = reinterpret_cast<llvm::IRBuilder<>* >(&_builder)->CreateCast(llvm::Instruction::SIToFP, _val, sFloatType.mLlvmType, "CastIntToFloat");
    return(floatVal);
}

llvm::Value* WNCharToFloatCast(llvm::Value* _val, llvm::IRBuilderBase& _builder) {
    return(WNIntToFloatCast(_val, _builder));
}

llvm::Value* WNNoCast(llvm::Value* _val, llvm::IRBuilderBase& _builder) {
    return(_val);
}

llvm::Value* WNFloatToIntCast(llvm::Value* _val, llvm::IRBuilderBase& _builder) {
    llvm::Value* intVal = reinterpret_cast<llvm::IRBuilder<>* >(&_builder)->CreateCast(llvm::Instruction::FPToSI, _val, sIntType.mLlvmType, "CastFloatToInt");
    return(intVal);
}

llvm::Value* WNFloatToCharCast(llvm::Value* _val, llvm::IRBuilderBase& _builder) {
    llvm::Value* intVal = reinterpret_cast<llvm::IRBuilder<>* >(&_builder)->CreateCast(llvm::Instruction::FPToSI, _val, sCharType.mLlvmType, "CastFloatToInt");
    return(intVal);
}

llvm::Value* WNIntToCharCast(llvm::Value* _val, llvm::IRBuilderBase& _builder) {
    llvm::Value* intVal = reinterpret_cast<llvm::IRBuilder<>* >(&_builder)->CreateIntCast(_val, sCharType.mLlvmType, WN_TRUE);
    return(intVal);
}

llvm::Value* WNCharToIntCast(llvm::Value* _val, llvm::IRBuilderBase& _builder) {
    llvm::Value* intVal = reinterpret_cast<llvm::IRBuilder<>* >(&_builder)->CreateIntCast(_val, sIntType.mLlvmType, WN_TRUE);
    return(intVal);
}

llvm::Value* WNCreateIntStore(WNScriptingEngineData& _scriptingData, llvm::Value* _val1, llvm::Value* _val2, bool _initial, WNStaticArithmetic _arithType) {
    WN_DEBUG_ASSERT(_arithType >= eWNStaticAssign && _arithType <= eWNStaticModEqual);
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    if(_arithType != eWNStaticAssign) {
        llvm::Value* val = builder->CreateLoad(_val2, _initial, "");
        switch(_arithType){
            case eWNStaticPlusEqual:
                _val1 = builder->CreateAdd(val, _val1);
                break;
            case eWNStaticMinusEqual:
                _val1 = builder->CreateSub(val, _val1);
                break;
            case eWNStaticTimesEqual:
                _val1 = builder->CreateMul(val, _val1);
                break;
            case eWNStaticDivideEqual:
                _val1 = builder->CreateSDiv(val, _val1);
                break;
            case eWNStaticModEqual:
                _val1 = builder->CreateSRem(val, _val1);
                break;
        }
    }
    
    return(reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder)->CreateStore(_val1, _val2));
}

llvm::Value* WNCreateFloatStore(WNScriptingEngineData& _scriptingData, llvm::Value* _val1, llvm::Value* _val2, bool _initial, WNStaticArithmetic _arithType) {
    WN_DEBUG_ASSERT(_arithType >= eWNStaticAssign && _arithType <= eWNStaticModEqual);
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    if(_arithType != eWNStaticAssign) {
        llvm::Value* val = builder->CreateLoad(_val2, _initial, "");
        switch(_arithType){
            case eWNStaticPlusEqual:
                _val1 = builder->CreateAdd(val, _val1);
                break;
            case eWNStaticMinusEqual:
                _val1 = builder->CreateSub(val, _val1);
                break;
            case eWNStaticTimesEqual:
                _val1 = builder->CreateMul(val, _val1);
                break;
            case eWNStaticDivideEqual:
                _val1 = builder->CreateFDiv(val, _val1);
                break;
            case eWNStaticModEqual:
                _val1 = builder->CreateFRem(val, _val1);
                break;
        }
    }
    return(reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder)->CreateStore(_val1, _val2));
}

llvm::Value* WNCreateCharStore(WNScriptingEngineData& _scriptingData, llvm::Value* _val1, llvm::Value* _val2, bool _initial, WNStaticArithmetic _arithType) {
    WN_DEBUG_ASSERT(_arithType >= eWNStaticAssign && _arithType <= eWNStaticModEqual);
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    if(_arithType != eWNStaticAssign) {
        llvm::Value* val = builder->CreateLoad(_val2, _initial, "");
        switch(_arithType){
            case eWNStaticPlusEqual:
                _val1 = builder->CreateAdd(val, _val1);
                break;
            case eWNStaticMinusEqual:
                _val1 = builder->CreateSub(val, _val1);
                break;
            case eWNStaticTimesEqual:
                _val1 = builder->CreateMul(val, _val1);
                break;
            case eWNStaticDivideEqual:
                _val1 = builder->CreateSDiv(val, _val1);
                break;
            case eWNStaticModEqual:
                _val1 = builder->CreateSRem(val, _val1);
                break;
        }
    }
    return(reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder)->CreateStore(_val1, _val2));
}

llvm::Value* WNCreateGenericStore(WNScriptingEngineData& _scriptingData, llvm::Value* _val1, llvm::Value* _val2, bool _initial, WNStaticArithmetic _arithType) {
    WN_RELEASE_ASSERT_DESC(_arithType == eWNStaticAssign, "Bad Arithmetic Type");
    return(reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder)->CreateStore(_val1, _val2));
}

llvm::Value* WNNullInitializer(WNScriptingEngineData&, llvm::Value* _val) {
    return(_val);
}

llvm::Value* WNNullLeaveScope(WNScriptingEngineData&, llvm::Value* _val) {
    return(_val);
}

llvm::Value* WNZeroCopySize(WNScriptingEngineData& _data, llvm::Value* _val) {
    llvm::Value* intVal = llvm::ConstantInt::get(_data.mTypeManager->GetIntPtrType(), 0);
    return(intVal);
}

llvm::Value* WNArrayCopySize(WNScriptingEngineData& _data, llvm::Value* _val) {
    WN_DEBUG_ASSERT(_val->getType()->getNumContainedTypes() == 1);
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_data.mBuilder);
    
    llvm::Type* arrayType = _val->getType()->getContainedType(0);
    llvm::Value* sizeLoc = builder->CreatePointerCast(_val, llvm::PointerType::getUnqual(_data.mTypeManager->GetType(eWNTypeInt)->mLlvmType));
    llvm::Value* size = builder->CreateLoad(sizeLoc, WN_FALSE, "arraySize");
    WN_INT32 extras = WNLLVMGetTypesPerType(_data, _data.mTypeManager->GetType(eWNTypeInt)->mLlvmType, arrayType);
    WN_INT64 typeSize = GET_DATA_LAYOUT(_data.mExecutionEngine)->getTypeAllocSize(arrayType);
    size = builder->CreateIntCast(size, _data.mTypeManager->GetIntPtrType(), false);
    size = builder->CreateAdd(size, llvm::ConstantInt::get(_data.mTypeManager->GetIntPtrType(), extras));
    size = builder->CreateMul(size, llvm::ConstantInt::get(_data.mTypeManager->GetIntPtrType(), typeSize));
    return(size);
}

llvm::Value* WNStructCopySize(WNScriptingEngineData& _data, llvm::Value* _val) {
    WN_DEBUG_ASSERT(_val->getType()->getNumContainedTypes());
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_data.mBuilder);

    llvm::Type* structType = _val->getType()->getContainedType(0);
    WN_INT64 typeSize = GET_DATA_LAYOUT(_data.mExecutionEngine)->getTypeAllocSize(structType);
    llvm::Value* size = llvm::ConstantInt::get(_data.mTypeManager->GetIntPtrType(), typeSize);
    return(size);
}

/////////////////////////////////////////////////////////////
static WNArithmeticStruct sStaticArithmetics[] = {
    {&sIntType,         &sFloatType,  WNIntFloatArithmetic,         WNIntFloatVerifyArithmetic,     WNIntFloatLLVMGeneration,     WNFloatToIntCast},
    {&sFloatType,       &sIntType,    WNFloatIntArithmetic,         WNFloatIntVerifyArithmetic,     WNFloatIntLLVMGeneration,     WNIntToFloatCast},
    {&sIntType,         &sIntType,    WNIntIntArithmetic,           WNIntIntVerifyArithmetic,       WNIntIntLLVMGeneration,       WNNoCast},
    {&sFloatType,       &sFloatType,  WNFloatFloatArithmetic,       WNFloatFloatVerifyArithmetic,   WNFloatFloatLLVMGeneration,   WNNoCast},
    {&sArrayType,       &sIntType,    WNInvalidArithmetic,          WNArrayIntVerifyArithmetic,     WNArrayIntLLVMGeneration,     WNNoCast},
    {&sStructType,      &sStructType, WNInvalidArithmetic,          WNStructStructVerifyArithmetic, WNStructStructLLVMGeneration, WNNoCast},
    {&sIntType,         &sCharType,   WNIntCharArithmetic,          WNIntCharVerifyArithmetic,      WNIntCharLLVMGeneration,      WNCharToIntCast},
    {&sFloatType,       &sCharType,   WNFloatCharArithmetic,        WNFloatCharVerifyArithmetic,    WNFloatCharLLVMGeneration,    WNCharToFloatCast},
    {&sCharType,        &sIntType,    WNCharIntArithmetic,          WNCharIntVerifyArithmetic,      WNCharIntLLVMGeneration,      WNIntToCharCast},
    {&sCharType,        &sFloatType,  WNCharFloatArithmetic,        WNCharFloatVerifyArithmetic,    WNCharFloatLLVMGeneration,    WNFloatToCharCast},
    {&sArrayType,       &sCharType,   WNInvalidArithmetic,          WNArrayCharVerifyArithmetic,    WNArrayCharLLVMGeneration,    WNNoCast},
    {&sCharType,        &sCharType,   WNCharCharArithmetic,         WNCharCharVerifyArithmetic,     WNCharCharLLVMGeneration,     WNNoCast}
};

static WNTypeCheckStruct sStaticTypeChecks[] = {
    {&sFloatType, WNFloatValid, WNCreateFloatConstant, WNCreateFloatStore, WNZeroCopySize },
    {&sIntType, WNAlwaysValid, WNCreateIntConstant, WNCreateIntStore, WNZeroCopySize },
    {&sCharType, WNAlwaysValid, WNCreateCharConstant, WNCreateCharStore, WNZeroCopySize },
    {&sArrayType, WN_NULL, WN_NULL, WNCreateGenericStore, WNArrayCopySize },
    {&sStructType, WNAlwaysValid, WN_NULL, WNCreateGenericStore, WNStructCopySize }
};

static WNArithmeticStruct sNullArithmeticType = {
    WN_NULL,
    WN_NULL,
    WNInvalidArithmetic,
    WNInvalidDynamicArithmetic,
    WNInvalidLLVMOperation,
    WNInvalidLLVMCast
};

static WNArithmeticStruct sEquivalentArthmeticType = {
    WN_NULL,
    WN_NULL,
    WNInvalidArithmetic,
    WNEqualDynamicArithmetic,
    WNInvalidLLVMOperation,
    WNEqualLLVMCast
};

static WNTypeCheckStruct sNullTypeCheck = {
    WN_NULL,
    WNInvalidTypeCheck,
    WNInvalidConstFunction,
    WNCreateGenericStore
};

WNTypeManager::WNTypeManager(WNIdentifierMap& _identifierMap):
    mInitializing(WN_FALSE),
    mIdentifierMap(_identifierMap) {
}


WN_BOOL WNTypeManager::TypeExists(WNTypeElement _type) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    WN_DEBUG_ASSERT(WNIsValid(_type));
    WN_BOOL found = WN_FALSE;
    if (_type->mType == eWNTypeUser) {
        WNTypeElementList::iterator i = std::find_if(mTypeMap.begin(), mTypeMap.end(), WNUserTypeCompare(_type));
            //WNFind<WNTypeElementList::iterator, WNTypeElement, UserTypeComparisonTest>(mTypeMap.begin(), mTypeMap.end(), type);
            if(i != mTypeMap.end()) {
                found = WN_TRUE;
            }
       
    } else {
        found = (_type->mType < eWNTypeMAX && _type->mType > eWNTypeInvalid);
    }
    
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    return(found);
}

WNTypeElement WNTypeManager::GetType(WNVariableType _type, WNIdentifierElement _ident){
    switch(_type) {
    case eWNTypeInt: {
            return(&sIntType);
        }
        break;
    case eWNTypeFloat: {
            return(&sFloatType);
        }
        break;
    case eWNTypeChar: {
            return(&sCharType);
        }
        break;
    case eWNTypeUser: {
            return(GetUserTypeByName(_ident));
        }
    case eWNTypeBool: {
            return(&sBoolType);
        }
    default: {
            return(WNInvalidType);
        }
        break;
    }
}
 
WNTypeElement WNTypeManager::AddUserType(WNIdentifierElement _type) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        if (!mInitializing) {
            mTypeManagerLock.Unlock();
        }
    #endif//WNSCRIPTING_MULTITHREADED_PARSING

    WN_DEBUG_ASSERT(WNIsValid(_type));
    WNTypeElement retval = WNInvalidType;

    __WNTypeElementInternal elem;
    elem.mType = eWNTypeUser;
    elem.mUserTypeName = _type;

    WNTypeElementList::iterator i = std::find_if(mTypeMap.begin(), mTypeMap.end(), WNUserTypeCompare(&elem));
      //  WNFind<WNTypeElementList::iterator, WNTypeElement, UserTypeComparisonTest>(mTypeMap.begin(), mTypeMap.end(), &elem);

    if (i != mTypeMap.end()) {
        retval = (*i);
    } else {
        WNTypeElement element = static_cast<WNTypeElement>(WNMalloc(sizeof(__WNTypeElementInternal)));
        element->mType = eWNTypeUser;
        element->mUserTypeName = _type;
        element->mStructTypes = WN_NULL;
        element->mSubTypeNames = WN_NULL;
        element->mIsStruct = WN_FALSE;
        mTypeMap.push_back(element);
        retval = element;
    }

#ifdef WN_SCRIPTING_MULTITHREADED_PARSING
    if (!mInitializing) {
        mTypeManagerLock.Unlock();
    }
#endif//WNSCRIPTING_MULTITHREADED_PARSING
    return(retval);
}

WNIdentifierElement WNTypeManager::GetUserTypeName(WNTypeElement _userType) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    WN_DEBUG_ASSERT(WNIsValid(_userType));
    WNIdentifierElement elem = WNInvalidIdentifier;
    
    WNTypeElementList::iterator loc = std::find_if(mTypeMap.begin(), mTypeMap.end(), WNUserTypeCompare(_userType));
    if (loc != mTypeMap.end()) {
        elem = (*loc)->mUserTypeName;
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    return(elem);
}


WNTypeElement WNTypeManager::GetUserTypeByName(WNIdentifierElement _name) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        if (!mInitializing) {
            mTypeManagerLock.Lock();
        }
    #endif

    WN_DEBUG_ASSERT(WNIsValid(_name ));
    WNTypeElement elem = WNInvalidType;
    

    __WNTypeElementInternal element;
    element.mUserTypeName = _name;
    element.mSubType = 0;

    WNTypeElementList::iterator loc = std::find_if(mTypeMap.begin(), mTypeMap.end(), WNUserTypeCompare(&element));
    if (loc != mTypeMap.end()) {
        elem = (*loc);
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    return(elem);
}

WNStaticArithmeticFunction WNTypeManager::GetStaticArithmeticFunction(WNTypeElement _elem1, WNTypeElement _elem2) {
    WN_DEBUG_ASSERT(WNIsValid(_elem1) && WNIsValid(_elem2));
   
    WNArithmeticStruct* arithStruct = GetStaticArithmeticStruct(_elem1, _elem2);

    if (arithStruct) {
        return(arithStruct->mStaticArithmetic);
    }

    return(WN_NULL);
}

WNGenerateLLVMOperation WNTypeManager::GetLLVMOperationGenerator(WNTypeElement _elem1, WNTypeElement _elem2) {
    WN_DEBUG_ASSERT(WNIsValid(_elem1) && WNIsValid(_elem2));

    WNArithmeticStruct* arithStruct = GetStaticArithmeticStruct(_elem1, _elem2);

    if (arithStruct) {
        return(arithStruct->mGenerateLLVMOperation);
    }

    return(WN_NULL);
}

WNGenerateLLVMCast WNTypeManager::GetLLVMCastGenerator(WNTypeElement _to, WNTypeElement _from) {
    WN_DEBUG_ASSERT(WNIsValid(_to) && WNIsValid(_from));

    WNArithmeticStruct* arithStruct = GetStaticArithmeticStruct(_to, _from);
    
    if (arithStruct) {
        return(arithStruct->mGenerateCast);
    }

    return(WN_NULL);
}

WNStaticTypeCheckFunction WNTypeManager::GetStaticTypeCheckFunction(WNTypeElement _elem) {
    WN_DEBUG_ASSERT(WNIsValid(_elem));

    WNTypeCheckStruct* typeCheckStruct = GetStaticTypeCheckStruct(_elem);
    
    if (typeCheckStruct) {
        return(typeCheckStruct->mStaticTypeCheck);
    }

    return(WN_NULL);
}

WNCreateConstantFunction  WNTypeManager::GetLLVMCreateConstant(WNTypeElement _arg) {
    WN_DEBUG_ASSERT(WNIsValid(_arg));

    WNTypeCheckStruct * typeCheckStruct = GetStaticTypeCheckStruct(_arg);

    if (typeCheckStruct) {
        return(typeCheckStruct->mCreateConstant);
    }

    return(WN_NULL);
}

WNGetCopySize WNTypeManager::GetLLVMCopySize(WNTypeElement _arg) {
    WN_DEBUG_ASSERT(WNIsValid(_arg));

    WNTypeCheckStruct* typeCheckStruct = GetStaticTypeCheckStruct(_arg);

    if (typeCheckStruct) {
        return(typeCheckStruct->mGetCopySize);
    }

    return(WN_NULL);
}

WNCreateStore WNTypeManager::GetLLVMStoreInst(WNTypeElement _arg) {
    WN_DEBUG_ASSERT(WNIsValid(_arg));

    WNTypeCheckStruct * typeCheckStruct = GetStaticTypeCheckStruct(_arg);

    if (typeCheckStruct) {
        return(typeCheckStruct->mCreateStore);
    }

    return(WN_NULL);
}

WNVerifyDynamicArithmetic WNTypeManager::GetDynamicArithmeticVerification(WNTypeElement _elem1, WNTypeElement _elem2) {
    WN_DEBUG_ASSERT(WNIsValid(_elem1) && WNIsValid(_elem2));
   
    WNArithmeticStruct* arithStruct = GetStaticArithmeticStruct(_elem1, _elem2);

    if (arithStruct) {
        return(arithStruct->mVerifyDynamicArithmetic);
    }

    return(WN_NULL);
}

WN_VOID WNTypeManager::Initialize(const WNScriptingEngineData& _initData) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Lock();
        mTypeCheckLock.Lock();
        mArithmeticLock.Lock();
    #endif

    INITIALIZE_TYPE(sIntType, eWNTypeInt, llvm::Type::getInt32Ty(llvm::getGlobalContext()), WN_FALSE, WN_FALSE);
    INITIALIZE_TYPE(sCharType, eWNTypeChar, llvm::Type::getInt8Ty(llvm::getGlobalContext()), WN_FALSE, WN_FALSE);
    INITIALIZE_TYPE(sFloatType, eWNTypeFloat, llvm::Type::getFloatTy(llvm::getGlobalContext()), WN_FALSE, WN_FALSE);
    INITIALIZE_TYPE(sBoolType, eWNTypeBool, llvm::IntegerType::get(llvm::getGlobalContext(), 1), WN_FALSE, WN_FALSE);
    INITIALIZE_TYPE(sArrayType, eWNTypeArray, llvm::Type::getInt32Ty(llvm::getGlobalContext()), WN_FALSE, WN_FALSE);
    INITIALIZE_TYPE(sStructType, eWNTypeStruct, llvm::Type::getInt32Ty(llvm::getGlobalContext()), WN_TRUE, WN_FALSE);
    
    mInitializing = WN_TRUE;

    mPointerInteger = GET_DATA_LAYOUT(_initData.mExecutionEngine)->GET_INT_PTR_TYPE(llvm::getGlobalContext());

    for (int i = 0; i < WN_ARRAY_LENGTH(sStaticArithmetics); ++i) {
        mArithmeticStructList.push_back(&sStaticArithmetics[i]);
    }

    for (int i = 0; i < WN_ARRAY_LENGTH(sStaticTypeChecks); ++i) {
        mTypeCheckStructList.push_back(&sStaticTypeChecks[i]);
    }

    for (int i = 0; i < WN_ARRAY_LENGTH(sRegisteredTypeNames); ++i) {
        sRegisteredTypeNames[i].type->mUserTypeName = mIdentifierMap.GetIdentifier(sRegisteredTypeNames[i].name, WNStrLen(sRegisteredTypeNames[i].name));    
        sRegisteredTypeNames[i].type->mSubType = WN_NULL;
        mTypeMap.push_back(sRegisteredTypeNames[i].type);
    }

    for (int i = 0; i < WN_ARRAY_LENGTH(sRegisteredNativeTypes); ++i) {
        sRegisteredNativeTypes[i].mUserType = mIdentifierMap.GetIdentifier(sRegisteredNativeTypes[i].mUserType, WNStrLen(sRegisteredNativeTypes[i].mUserType));
        mNativeTypeList.push_back(&sRegisteredNativeTypes[i]);
    }

    mInitializing = WN_FALSE;

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mArithmeticLock.Unlock();
        mTypeCheckLock.Unlock();
        mTypeManagerLock.Unlock();
    #endif
}

WNArithmeticStruct* WNTypeManager::GetStaticArithmeticStruct(WNTypeElement _elem1, WNTypeElement _elem2) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        if (!mInitializing) {
            mArithmeticLock.Lock();
        }
    #endif

    WNArithmeticStruct* ret = &sNullArithmeticType;

    WNArithmeticStructList::iterator i;

    for (i = mArithmeticStructList.begin(); i != mArithmeticStructList.end(); ++i) {
        if (_elem1 == (*i)->mLHS && _elem2 == (*i)->mRHS) {
            break;
        }
    }

    if (i != mArithmeticStructList.end()) {
        WNArithmeticStruct* s = (*i);
        mArithmeticStructList.erase(i);
        mArithmeticStructList.push_front(s);
        ret = s;
    }

    if (ret == &sNullArithmeticType && _elem1 == _elem2) {
        ret = &sEquivalentArthmeticType;
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        if (!mInitializing) {
            mArithmeticLock.Unlock();
        }
    #endif

    return(ret);
}

WNTypeCheckStruct*  WNTypeManager::GetStaticTypeCheckStruct(WNTypeElement _elem) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        if (!mInitializing) {
            mTypeCheckLock.Lock();
        }
    #endif

    WNTypeCheckStruct* ret = &sNullTypeCheck;
    WNTypeCheckStructList::iterator i;

    for (i = mTypeCheckStructList.begin(); i != mTypeCheckStructList.end(); ++i) {
        if (_elem == (*i)->type) {
            break;
        }
    }

    if (i != mTypeCheckStructList.end()) {
        WNTypeCheckStruct* s = (*i);

        mTypeCheckStructList.erase(i);
        mTypeCheckStructList.push_front(s);

        ret = s;
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        if (!mInitializing) {
            mTypeCheckLock.Unlock();
        }
    #endif

    return ret;
}

//TODO:
//Add the intPointerType to the initialization of the type manager
WNTypeElement WNTypeManager::GetArrayType(WNTypeElement _arrayType) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Lock();
    #endif

    WNArrayHashMap::const_iterator cIt;
    for(cIt = mArrayHashMap.begin(); cIt != mArrayHashMap.end(); ++cIt) {
        if ((*cIt)->mSubType == _arrayType) {
            break;
        }
    }

    if (cIt != mArrayHashMap.end()) {
        #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
            mTypeManagerLock.Unlock();
        #endif

        return(*cIt);
    }

    WNTypeElement element = static_cast<WNTypeElement>(WNMalloc(sizeof(__WNTypeElementInternal)));
    *element = sArrayType;
    element->mSubType = _arrayType;
    element->mLlvmType = llvm::PointerType::getUnqual(_arrayType->mLlvmType);
    element->mStructTypes = WN_NULL;
    element->mSubTypeNames = WN_NULL;
    element->mIsStruct = WN_FALSE;

    WNArithmeticStruct str = {&sArrayType, &sIntType,   WNInvalidArithmetic, WNArrayIntVerifyArithmetic, WNArrayIntLLVMGeneration, WNNoCast};
    WNArithmeticStruct* arithStr = static_cast<WNArithmeticStruct*>(WNMalloc(sizeof(WNArithmeticStruct)));
    (*arithStr) = str;
    arithStr->mLHS = element;

    WNTypeCheckStruct typeCheck = {&sArrayType, WN_NULL, WN_NULL, WNCreateGenericStore, WNArrayCopySize };
    WNTypeCheckStruct* typeStr = static_cast<WNTypeCheckStruct*>(WNMalloc(sizeof(WNTypeCheckStruct)));
    (*typeStr) = typeCheck;
    typeStr->type = element;

    mTypeMap.push_back(element);
    mArrayHashMap.push_back(element);
    mArithmeticStructList.push_back(arithStr);
    mTypeCheckStructList.push_back(typeStr);
    WNNativeElementType* nativeType = static_cast<WNNativeElementType*>(WNMalloc(sizeof(WNNativeElementType)));
    nativeType->mUserType = "WN_POINTER";
    nativeType->mScriptType = element;
    mNativeTypeList.push_back(nativeType);

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    return(element);
}

WN_BOOL WNTypeManager::IsArrayType(WNTypeElement _type) const {
    return(_type->mSubType != WN_NULL);
}

WNTypeElement WNTypeManager::GetSubType(WNTypeElement _type) const {
    if(IsArrayType(_type)){
        return(_type->mSubType);
    }
    return(WN_NULL);
}

WNNativeElementType* WNTypeManager::GetNativeArrayFromWNType(WNIdentifierElement _nativeType) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Lock();
    #endif

    WNNativeElementList::iterator i;
    WNNativeElementList::iterator nonArrayVersion;
    for(i = mNativeTypeList.begin(); i != mNativeTypeList.end(); ++i) {
        if (_nativeType == (*i)->mUserType && (*i)->mArray) {
            break;
        } else if (_nativeType == (*i)->mUserType) {
            nonArrayVersion = i;
        }
    }
    
    WNNativeElementType* ret = WN_NULL;
    if(i == mNativeTypeList.end()) {
        if(nonArrayVersion != mNativeTypeList.end()) {
            WNNativeElementType* newElement = static_cast<WNNativeElementType*>(WNMalloc(sizeof(WNNativeElementType)));
            WNMemCpy(newElement, *nonArrayVersion, sizeof(WNNativeElementType));
            newElement->mArray = WN_TRUE;
            newElement->mScriptType = GetArrayType(newElement->mScriptType);
            mNativeTypeList.push_front(newElement);
            ret = newElement;
        }
    } else {
       WNNativeElementType* elem = (*i);
       ret = elem;
       mNativeTypeList.erase(i);
       mNativeTypeList.push_front(elem);
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    return(ret);
}

WNNativeElementType* WNTypeManager::GetNativeFromWNType(WNIdentifierElement _nativeType) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Lock();
    #endif

    WNNativeElementList::iterator i;
    for(i = mNativeTypeList.begin(); i != mNativeTypeList.end(); ++i) {
        if (_nativeType == (*i)->mUserType && (*i)->mArray == false) {
            break;
        }
    }

    WNNativeElementType* ret = WN_NULL;
    if(i != mNativeTypeList.end()) {
       WNNativeElementType* elem = (*i);
       ret = elem;
       mNativeTypeList.erase(i);
       mNativeTypeList.push_front(elem);
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    return(ret);
}

WNNativeElementType* WNTypeManager::GetNativeFromType(WNTypeElement _type) {
    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Lock();
    #endif

    WNNativeElementList::iterator i;
    for(i = mNativeTypeList.begin(); i != mNativeTypeList.end(); ++i) {
        if (_type == (*i)->mScriptType) {
            break;
        }
    }

    WNNativeElementType* ret = WN_NULL;
    if(i != mNativeTypeList.end()) {
        WNNativeElementType* elem = (*i);
        ret = elem;
        mNativeTypeList.erase(i);
        mNativeTypeList.push_front(elem);
    }

    #ifdef WN_SCRIPTING_MULTITHREADED_PARSING
        mTypeManagerLock.Unlock();
    #endif

    return(ret);
}

llvm::Type* WNTypeManager::GetIntPtrType() const {
    return(mPointerInteger);
}

WN_BOOL WNTypeManager::IsReferenceType(WNTypeElement _type) const {
    return(_type->mSubType != WN_NULL || _type->mIsStruct);
}

void WNTypeManager::AddStructBody(WNTypeElement _elem, const std::vector<WNTypeElement>& _typeList, const std::vector<WNIdentifierElement>& _argumentNames) {
    std::vector<llvm::Type*> llTypes;
    for(std::vector<WNTypeElement>::const_iterator i = _typeList.begin(); i != _typeList.end(); ++i) {
        llTypes.push_back((*i)->mLlvmType);
    }
    _elem->mSubTypeNames = WN_NEW(std::vector<WNIdentifierElement>());
    _elem->mStructTypes = WN_NEW(std::vector<WNTypeElement>());
    *(_elem->mStructTypes) = _typeList;
    *(_elem->mSubTypeNames) = _argumentNames;
    const WN_CHAR* sName = mIdentifierMap.GetStringFromIdentifier(_elem->mUserTypeName);
    _elem->mStructType->setBody(llTypes);
    _elem->mStructType->setName(sName);
    _elem->mLlvmType = llvm::PointerType::getUnqual(_elem->mStructType);

    WNTypeCheckStruct checkStruct = {&sStructType, WNAlwaysValid, WN_NULL, WNCreateGenericStore, WNStructCopySize };
    WNTypeCheckStruct* newCheckStruct = static_cast<WNTypeCheckStruct*>(WNMalloc(sizeof(WNTypeCheckStruct)));
    *newCheckStruct = checkStruct;
    newCheckStruct->type = _elem;
    mTypeCheckStructList.push_back(newCheckStruct);
}

WNTypeElement WNTypeManager::RegisterStructType(WNIdentifierElement _structName) {
    __WNTypeElementInternal* retval = WN_NULL;
    __WNTypeElementInternal elem;
    elem.mType = eWNTypeUser;
    elem.mUserTypeName = _structName;
    elem.mSubType = WN_NULL;       
    elem.mIsStruct = WN_TRUE;

    WNTypeElementList::iterator i = std::find_if(mTypeMap.begin(), mTypeMap.end(), WNUserTypeCompare(&elem));
    if (i != mTypeMap.end()) {
        return(WN_NULL);
    } else {
        WNTypeElement element = static_cast<WNTypeElement>(WNMalloc(sizeof(__WNTypeElementInternal)));
        (*element) = sStructType;
        element->mType = eWNTypeUser;
        element->mUserTypeName = _structName;
        element->mStructTypes = WN_NULL;
        element->mSubTypeNames = WN_NULL;
        element->mIsStruct = WN_TRUE;
        
        mTypeMap.push_back(element);
        retval = element;


        WNNativeElementType nativeType = {&sStructType, "WN_POINTER", 'P', WN_FALSE};
        WNNativeElementType* newType = static_cast<WNNativeElementType*>(WNMalloc(sizeof(WNNativeElementType)));
        WNMemCpy(newType, &nativeType, sizeof(WNNativeElementType));
        newType->mScriptType = element;
        mNativeTypeList.push_back(newType);
    }

    llvm::StructType* s = llvm::StructType::get(llvm::getGlobalContext());  
    retval->mStructType = s;
    return(retval);
}

WN_BOOL WNTypeManager::IsStructType(WNTypeElement _structType) const {
    return(_structType && _structType->mIsStruct);
}

WN_BOOL WNTypeManager::GetStructValueLocation(WNTypeElement _elem, WNIdentifierElement _ident, WNTypeElement& _retElem, WN_SIZE_T& _outVal) {
    for(WN_SIZE_T i = 0; i < _elem->mSubTypeNames->size(); ++i){
        if((*_elem->mSubTypeNames)[i] == _ident){
            _outVal = i;
            _retElem = (*_elem->mStructTypes)[i];
            return(WN_TRUE);
        }
    }
    return(WN_FALSE);
}

llvm::Value* WNTypeManager::RecurseCreateArrayAlloc(WNScriptingEngineData& _data, WNTypeElement _type, const std::vector<llvm::Value*>& _amounts, WN_BOOL _heap, WN_SIZE_T _arrayIndex, WN_BOOL _suppressLastCreation) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_data.mBuilder);
    WNTypeElement e = _type;
    
    WN_BOOL aggregate = WN_FALSE;
    //TODO: Fix this up, we end up doing n^2 work on the creation of these. 
    //      not a HUUUGE issue, since we dont expect people to have for example 25 array indices deep,
    //      still not IDEAL
    for(WN_SIZE_T i = _arrayIndex; i < _amounts.size(); ++i) {
        aggregate = WN_TRUE;
        e = GetArrayType(e);
    }

    if (aggregate) {
        WNTypeElement intType = GetType(eWNTypeInt);
        WN_SIZE_T typesPerType = WNLLVMGetTypesPerType(_data, intType->mLlvmType, e->mSubType->mLlvmType);

        llvm::Value * v = llvm::ConstantInt::get(mPointerInteger, typesPerType);
        llvm::Value * vTot = builder->CreateAdd(v, builder->CreateIntCast(_amounts[_arrayIndex], mPointerInteger, false));

        llvm::Value * allocVal = GenerateLLVMAllocation(_heap, _data, e->mLlvmType->getContainedType(0), vTot);

        llvm::Value * allocValAsInt = builder->CreatePointerCast(allocVal, llvm::PointerType::getUnqual(intType->mLlvmType));
        builder->CreateStore(_amounts[_arrayIndex], allocValAsInt);
        
        if(IsReferenceType(e->mSubType) && (!_suppressLastCreation || _arrayIndex < (_amounts.size() - 1))) {
            llvm::Value * vLoc = builder->CreateAlloca(mPointerInteger);
            builder->CreateStore(v, vLoc);

            llvm::BasicBlock* loopHeader = llvm::BasicBlock::Create(llvm::getGlobalContext(), "loopHeader", _data.mScopedVariableList->GetCurrentFunction()->mLLVMFunction);
            llvm::BasicBlock* loopBody = llvm::BasicBlock::Create(llvm::getGlobalContext(), "loopBody", _data.mScopedVariableList->GetCurrentFunction()->mLLVMFunction);
            llvm::BasicBlock* loopEnd = llvm::BasicBlock::Create(llvm::getGlobalContext(), "loopEnd", _data.mScopedVariableList->GetCurrentFunction()->mLLVMFunction);

            builder->CreateBr(loopHeader);
            builder->SetInsertPoint(loopHeader);
            llvm::Value* curIndex = builder->CreateLoad(vLoc);
            llvm::Value* comp = builder->CreateICmpSLT(curIndex, vTot);
            builder->CreateCondBr(comp, loopBody, loopEnd);

            builder->SetInsertPoint(loopBody);

            std::vector<llvm::Value*> gepValues;
            llvm::Value* gepValue = builder->CreateIntCast(curIndex, builder->getInt32Ty(), WN_TRUE, "ConvertToInt");

            llvm::Value* valLoc = builder->CreateGEP(allocVal, gepValue);
            llvm::Value* targetVal = RecurseCreateArrayAlloc(_data, _type, _amounts, _heap, _arrayIndex + 1, _suppressLastCreation);

            builder->CreateStore(targetVal, valLoc);

            llvm::Value* newComp = builder->CreateAdd(curIndex, llvm::ConstantInt::get(mPointerInteger, 1));
            builder->CreateStore(newComp, vLoc);
            builder->CreateBr(loopHeader);
            builder->SetInsertPoint(loopEnd);
        }
        return(allocVal);
    } else {
        if (IsStructType(_type)) {
            return(GenerateLLVMAllocation(_heap, _data, e->mLlvmType->getContainedType(0)));
        }
    }
    return(WN_NULL);
}

llvm::Value* WNTypeManager::CreateAllocation(WNScriptingEngineData& _data, WNTypeElement _type, const std::vector<llvm::Value*>& _amounts, WN_BOOL _heap, WN_BOOL _suppressLastCreation){
    if(_amounts.size() == 0) {
        _heap = _heap | _type->mForceHeap;
        return(GenerateLLVMAllocation(_heap, _data, _type->mLlvmType->getContainedType(0)));
    } else {
        return(RecurseCreateArrayAlloc(_data, _type, _amounts, _heap, 0, _suppressLastCreation));
    }
}

llvm::Value* WNTypeManager::GenerateLLVMAllocation(WN_BOOL _heap, WNScriptingEngineData &_data, llvm::Type* _type, llvm::Value* _numElems) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_data.mBuilder);
    WNFunction* func = _data.mFunctionManager->GetMallocFunction();
    const llvm::DataLayout* td = GET_DATA_LAYOUT(_data.mExecutionEngine);
    llvm::Type* llType = _type;
    if (_heap) {
        llvm::Type* intPtrType = _data.mTypeManager->GetIntPtrType();
        llvm::Constant* llConst = llvm::ConstantInt::get(intPtrType, td->getTypeAllocSize(llType));
        if(_numElems == WN_NULL) {
            _numElems = llvm::ConstantInt::get(intPtrType, 1);
        } else {
            _numElems = builder->CreateIntCast(_numElems, intPtrType, true, "ConvertForMul");
        }
        _numElems = builder->CreateMul(_numElems, llConst);
        std::vector<llvm::Value*> params;
        params.push_back(_numElems);
        llvm::Value* retVal = builder->CreateCall(func->mLlvmFunction, params);
        retVal = builder->CreatePointerCast(retVal, llvm::PointerType::getUnqual(llType));
        return(retVal);
    } else {
        llvm::Value* retVal = builder->CreateAlloca(llType, _numElems);
        return(retVal);
    }
}

llvm::Value* WNTypeManager::CreateConstAtomicAdd(WNScriptingEngineData& _data, llvm::Value* _ptr, WN_SIZE_T _addAmount) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_data.mBuilder);
    #ifdef _WN_ANDROID
    std::vector<llvm::Type*> memcpyTypes;
    memcpyTypes.push_back(_ptr->getType());
    memcpyTypes.push_back(_ptr->getType()->getContainedType(0));
 
    llvm::Function* fnc = llvm::Intrinsic::getDeclaration(_data.mModule, llvm::Intrinsic::atomic_load_add, memcpyTypes);
    
    std::vector<llvm::Value*> args;
    args.push_back(_ptr);
    args.push_back(llvm::ConstantInt::get(_ptr->getType()->getContainedType(0), _addAmount));
    _ptr = builder->CreateCall(fnc, args, "");
    #else
    _ptr = builder->CreateAtomicRMW(llvm::AtomicRMWInst::Add, _ptr, llvm::ConstantInt::get(sIntType.mLlvmType, _addAmount), llvm::SequentiallyConsistent);
    #endif

    return(_ptr);
}

llvm::Value* WNTypeManager::CreateConstAtomicSubtract(WNScriptingEngineData& _data, llvm::Value* _ptr, WN_SIZE_T _subAmount) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_data.mBuilder);
    #ifdef _WN_ANDROID
    std::vector<llvm::Type*> memcpyTypes;
    memcpyTypes.push_back(_ptr->getType());
    memcpyTypes.push_back(_ptr->getType()->getContainedType(0));
 
    llvm::Function* fnc = llvm::Intrinsic::getDeclaration(_data.mModule, llvm::Intrinsic::atomic_load_sub, memcpyTypes);
    
    std::vector<llvm::Value*> args;
    args.push_back(_ptr);
    args.push_back(llvm::ConstantInt::get(_ptr->getType()->getContainedType(0), _subAmount));
    _ptr = builder->CreateCall(fnc, args, "");
    #else
    _ptr = builder->CreateAtomicRMW(llvm::AtomicRMWInst::Sub, _ptr, llvm::ConstantInt::get(sIntType.mLlvmType, _subAmount), llvm::SequentiallyConsistent);
    #endif
    return(_ptr);
}
